#include <QApplication>
#include <QPainter>
#include <QMouseEvent>
#include <QDesktopServices>
#include <QTextBlock>
#include <QMenu>
#include <QPlainTextEdit>
#include <QTextLayout>
#include <QItemSelectionModel>
#include <QMessageBox>
#include <QAbstractTextDocumentLayout>
#include "commentsview.h"
#include "commentsmodel.h"
#include "mainwin.h"

#define COMMENT_PADDING 5

static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth)
{
	qreal height = 0;
	qreal widthUsed = 0;
	textLayout.beginLayout();
	while (true) {
		QTextLine line = textLayout.createLine();
		if (!line.isValid())
			break;
		line.setLineWidth(lineWidth);
		line.setPosition(QPointF(0, height));
		height += line.height();
		widthUsed = qMax(widthUsed, line.naturalTextWidth());
	}
	textLayout.endLayout();
	return QSizeF(widthUsed, height);
}

CommentItemDelegate::CommentItemDelegate(QObject* parent) : QStyledItemDelegate(parent)
{
}

CommentItemDelegate::~CommentItemDelegate()
{
}

void CommentItemDelegate::drawCommentContent(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect, const QString& text) const
{
	//const QWidget *widget = option->widget;
	const int textMargin = 0;//widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;

	QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
//	const bool wrapText = option->features & QStyleOptionViewItemV2::WrapText;
	QTextOption textOption;
	textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);//: QTextOption::ManualWrap);
	textOption.setFlags(QTextOption::ShowLineAndParagraphSeparators);
	textOption.setTextDirection(option->direction);
	textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment));
	QTextLayout textLayout;
	textLayout.setTextOption(textOption);
	textLayout.setFont(option->font);
	textLayout.setText(text);

	QSizeF textLayoutSize = viewItemTextLayout(textLayout, textRect.width());

	QString elidedText;
	qreal height = 0;
	qreal width = 0;
	int elidedIndex = -1;
	const int lineCount = textLayout.lineCount();
	for (int j = 0; j < lineCount; ++j) {
		const QTextLine line = textLayout.lineAt(j);
		if (j + 1 <= lineCount - 1) {
			const QTextLine nextLine = textLayout.lineAt(j + 1);
			if ((nextLine.y() + nextLine.height()) > textRect.height()) {
//				int start = line.textStart();
//				int length = line.textLength() + nextLine.textLength();
				elidedText = QFontMetrics(option->font).elidedText(textLayout.text(), option->textElideMode, textRect.width());
				height += line.height();
				width = textRect.width();
				elidedIndex = j;
				break;
			}
		}
		if (line.naturalTextWidth() > textRect.width()) {
//			int start = line.textStart();
//			int length = line.textLength();
			elidedText = QFontMetrics(option->font).elidedText(textLayout.text(), option->textElideMode, textRect.width());
			height += line.height();
			width = textRect.width();
			elidedIndex = j;
			break;
		}
		width = qMax<qreal>(width, line.width());
		height += line.height();
	}

	const QRect layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment,
		QSize(int(width), int(height)), textRect);
	const QPointF position = layoutRect.topLeft();
	for (int i = 0; i < lineCount; ++i) {
		const QTextLine line = textLayout.lineAt(i);
		if (i == elidedIndex) {
			qreal x = position.x() + line.x();
			qreal y = position.y() + line.y() + line.ascent();
			p->save();
			p->setFont(option->font);
			p->drawText(int(x), int(y), elidedText);
			p->restore();
			break;
		}
		line.draw(p, position);
	}
}

bool CommentItemDelegate::eventFilter(QObject * object, QEvent * event)
{
	QWidget *editor = qobject_cast<QWidget*>(object);
	if (editor && event->type() == QEvent::KeyPress) {
		switch (static_cast<QKeyEvent *>(event)->key()) {
			case Qt::Key_Enter:
			case Qt::Key_Return:
				emit commitData(editor);
				emit closeEditor(editor, QAbstractItemDelegate::SubmitModelCache);
				return true;
				break;
		}
	}
	return QStyledItemDelegate::eventFilter(editor, event);
}

QWidget * CommentItemDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const
{
	QPlainTextEdit* edit = new QPlainTextEdit(parent);
	edit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	edit->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
	return edit;
}


void CommentItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
	if (!index.isValid() || index.column() != 0) {
		QStyledItemDelegate::paint(painter, option, index);
		return;
	}
	QVariant data = index.data(CommentsModel::commentPointerRole);
	CommentsModel::CommentItem* item = (CommentsModel::CommentItem*)qVariantValue<void*>(data);
	if (!item)
		return;
	QStyleOptionViewItemV4 opt = option;
	initStyleOption(&opt, index);
	const QWidget *widget = opt.widget;
	QStyle *style = widget ? widget->style() : QApplication::style();
	style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);


	QFontMetrics fm(option.font);

	painter->save();
	QRect rcDraw = option.rect;
	rcDraw.adjust(COMMENT_PADDING, COMMENT_PADDING, -COMMENT_PADDING, -COMMENT_PADDING);
	/*rcDraw.adjust(style->pixelMetric(QStyle::PM_LayoutLeftMargin, &option), style->pixelMetric(QStyle::PM_LayoutTopMargin, &option),
	-style->pixelMetric(QStyle::PM_LayoutRightMargin, &option), -style->pixelMetric(QStyle::PM_LayoutBottomMargin, &option));*/
	QRect rcTop =rcDraw;
	rcTop.setBottom(rcTop.top() + fm.lineSpacing());
	QRect br;
	// date time
	QFont f = option.font;
	if (item->comment.unread())
	{
		f.setBold(true);
		painter->setFont(f);
	}
	painter->setPen(QColor(item->comment.unread() ? "#000000" : "#7a8288"));
	painter->drawText(rcTop, Qt::AlignRight | Qt::AlignVCenter | Qt::TextSingleLine, item->comment.dateDiff(), &br);

	// author
	//painter->setPen(QColor(item->comment->unread ? "#000000" : "#7a8288"));
	painter->setPen(QColor("#0089bf"));
	rcTop.setRight(br.left() - 3);
	painter->drawText(rcTop, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, 
		QFontMetrics(f).elidedText(item->comment.author(), Qt::ElideRight, rcTop.width()), &br);
	item->rcAuthor = br;

	// content;
	painter->setFont(option.font);
	QRect rcContent = rcDraw;
	rcContent.setTop(rcTop.bottom() + 3);
	rcContent.setBottom(rcContent.top() + item->doc.documentLayout()->documentSize().height());
	if (s_opt.m_showFullComments) {
		QAbstractTextDocumentLayout::PaintContext context;
		painter->translate(rcContent.topLeft());
		item->doc.documentLayout()->draw(painter, context);
		painter->translate(-rcContent.left(), -rcContent.top());
	}
	else {
		drawCommentContent(painter, &opt, rcContent, item->comment.content());
	}
	// draw post title
	QRect rcPost = rcDraw;
	rcPost.setTop(rcContent.bottom());
	rcPost.setBottom(rcContent.bottom() + fm.lineSpacing());
	painter->setPen(QColor("#7a8288"));
	painter->drawText(rcPost, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
		fm.elidedText("#" + item->comment.postTitle(), Qt::ElideRight, rcPost.width()), &br);
	item->rcPostTitle = br;
	/*if (option.state & QStyle::State_MouseOver)
	{
	}*/
	painter->restore();
}

QSize CommentItemDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
	QSize size(1, 0);

	QFontMetrics f(option.font);
	CommentsModel::CommentItem* item = CommentFilterModel::comment(index);
	if (s_opt.m_showFullComments && item) {
		item->doc.setPageSize(QSize(((QListView*)parent())->viewport()->size().width() - 2* COMMENT_PADDING, INT_MAX));
		QSize docSize = item->doc.documentLayout()->documentSize().toSize();
		size.setHeight(f.lineSpacing() * 3 + 2 * COMMENT_PADDING + docSize.height() + 3);
	}
	else {
		size.setHeight(f.lineSpacing() * 6 + 2 * COMMENT_PADDING + 3);
	}
	return size;
}

////////////////////////////////////////////////////////
// CommentsView
////////////////////////////////////////////////////////

CommentsView::CommentsView(QWidget* parent) : QListView(parent)
{
	setMouseTracking(true);
	m_mousePosition = CommentsView::none;

	m_commandBar = new QLabel(this);
	m_commandBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
	m_commandBar->setScaledContents(true);
	m_commandBar->setContentsMargins(0, 0, 0, 0);
	m_commandBar->setTextFormat(Qt::RichText);
	m_commandBar->setOpenExternalLinks(false);
	m_commandBar->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
	m_commandBar->setVisible(false);
	connect(m_commandBar, SIGNAL(linkActivated(const QString&)), SLOT(commandClicked(const QString&)));
}


void CommentsView::filterChanged(CommentData::CommentStatus status)
{
	switch(status) {
	case CommentData::approve:
		m_commandBar->setText("<html><body><a href=\"hold\" title=\"Unapprove\"><span style=\"color:#e6853a\">Unapprove</span></a> | <a href=\"reply\" style=\"color:#0089bf\">Reply</a> |"
			"<a href=\"edit\" style=\"color:#0089bf\">Edit</a> | <a href=\"spam\"><span style=\"color:#d30b3f\">Spam</span></a> | "
			"<a href=\"delete\"><span style=\"color:red\">Delete</span></a></body></html>");		
		break;
	case CommentData::hold:
		m_commandBar->setText("<html><body><a href=\"approve\"><span style=\"color:#006505\">Approve</span></a> | "
			"<a href=\"reply\" style=\"color:#0089bf\">Reply</a> | "
			"<a href=\"edit\" style=\"color:#0089bf\">Edit</a> | "
			"<a href=\"spam\"><span style=\"color:#d30b3f\">Spam</span></a> | "
			"<a href=\"delete\"><span style=\"color:red\">Delete</span></a></body></html>");	
		break;
	case CommentData::spam:
		m_commandBar->setText("<html><body><a href=\"edit\" style=\"color:#0089bf\">Edit</a> | "
			"<a href=\"hold\"><span style=\"color:#d30b3f\">Not Spam</span></a> | "
			"<a href=\"delete\"><span style=\"color:red\">Delete</span></a></body></html>");	
		break;
	}
	m_commandBar->setVisible(false);
}


CommentsView::~CommentsView()
{

}

void CommentsView::actDelete()
{
	CommentFilterModel* model = (CommentFilterModel*)this->model();
	QList<QPersistentModelIndex> indexes;
	foreach(QModelIndex index, selectionModel()->selectedIndexes()) {
		indexes << index;
	}
	if (model->filterStatus() != CommentData::spam) {
		QString message, title;
		if (indexes.count() == 1) {
			title = tr("Delete Comment");
			message = tr("Are you sure you want to delete this comment?");
		}
		else {
			title = tr("Delete Multiple Comments");
			message = tr("Are you sure you want to delete these %1 comment?").arg(indexes.count());
		}
		if (QMessageBox::Yes != QMessageBox::question(NULL, title, message, 
			QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) {
			return;
		}
	}
	foreach(QPersistentModelIndex index, indexes) {
		QPersistentModelIndex sourceIdx = model->mapToSource(index);
		CommentsModel* srcModel = (CommentsModel*)model->sourceModel();
		srcModel->deleteComment(sourceIdx);
	}
}

void CommentsView::actReply()
{
	CommentFilterModel* model = (CommentFilterModel*)this->model();
	QPersistentModelIndex sourceIdx = model->mapToSource(currentIndex());
	emit replyComment(sourceIdx);
}

void CommentsView::actApprove()
{
	setSelectedCommentsStatus(CommentData::approve);
}

void CommentsView::actHold()
{
	setSelectedCommentsStatus(CommentData::hold);
}

void CommentsView::actSpam()
{
	setSelectedCommentsStatus(CommentData::spam);
}

void CommentsView::actEdit()
{
	QModelIndex index = currentIndex();
	if (index.isValid()) {
		((QListView*)this)->edit(index);
	}
	m_commandBar->hide();
}

void CommentsView::setSelectedCommentsStatus(CommentData::CommentStatus status)
{
	CommentFilterModel* model = (CommentFilterModel*)this->model();
	QList<QPersistentModelIndex> indexes;
	foreach(QModelIndex index, selectionModel()->selectedIndexes()) {
		indexes << index;
	}
	foreach(QPersistentModelIndex index, indexes) {
		QPersistentModelIndex sourceIdx = model->mapToSource(index);
		CommentsModel* srcModel = (CommentsModel*)model->sourceModel();
		srcModel->editComment(sourceIdx, Comment::statusToString(status), QString());
	}
}

void CommentsView::commandClicked(const QString& cmd)
{
	QAction* action = MainWindow::instance()->action(cmd);
	action->trigger();
}

void CommentsView::editorDestroyed ( QObject * editor )
{
	QListView::editorDestroyed(editor);
	//m_editingIndex = QModelIndex();
}

bool CommentsView::edit(const QModelIndex & index, EditTrigger trigger, QEvent * event)
{
	bool bRet = QListView::edit(index, trigger, event);
	if (bRet) {
		//	m_editingIndex = index;
		m_commandBar->setVisible(false);
	}
	return bRet;
}

void CommentsView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
	QListView::rowsAboutToBeRemoved(parent, start, end);
}

void CommentsView::currentChanged ( const QModelIndex & current, const QModelIndex & previous )
{
	showCommandBar();
	// mark item as read.
	if (current.isValid()) {
		CommentFilterModel* model = (CommentFilterModel*)this->model();
		QPersistentModelIndex index = model->mapToSource(current);
		CommentsModel* srcModel = (CommentsModel*)model->sourceModel();
		srcModel->setData(index, false, CommentsModel::unreadRole);
	}
	QListView::currentChanged(current, previous);
}

void CommentsView::mouseMoveEvent(QMouseEvent *event)
{
	QModelIndex index = indexAt(event->pos());
	if (index.isValid()) {
		CommentsModel::CommentItem* item = CommentFilterModel::comment(index);
		if (item) {
			if (item->rcAuthor.contains(event->pos())) {
				if (m_mousePosition != CommentsView::author) {
					m_mousePosition = CommentsView::author;
					setCursor(Qt::PointingHandCursor);
					setToolTip(item->comment.authorUrl());
				}
			}
			/*	else if (item->rcPostTitle.contains(event->pos()))
			{
			if (m_mousePosition != CommentsView::postTitle)
			{
			m_mousePosition = CommentsView::postTitle;
			setCursor(Qt::PointingHandCursor);
			setToolTip(item->comment->post_title);
			}
			}*/
			else if (m_mousePosition != CommentsView::none) {
				m_mousePosition = CommentsView::none;
				//setToolTip(tr("Double click to edit this comment"));
				setCursor(Qt::ArrowCursor);
			}
		}
	}
	else {
		//m_commandBar->hide();
	}
	QListView::mouseMoveEvent(event);
}

void CommentsView::leaveEvent ( QEvent * event )
{
	QListView::leaveEvent(event);
}

void CommentsView::mousePressEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton) {
		QModelIndex index = indexAt(event->pos());
		if (index.isValid()) {
			if (m_mousePosition == CommentsView::author) {
				CommentsModel::CommentItem* item = CommentFilterModel::comment(index);
				if (item) {

					QDesktopServices::openUrl(item->comment.authorUrl());
				}
			}
		}
	}
	QListView::mousePressEvent(event);
}

void CommentsView::scrollContentsBy ( int dx, int dy )
{
	showCommandBar();
	QListView::scrollContentsBy(dx, dy);
}


void CommentsView::setModel(QAbstractItemModel * model)
{
	connect(model, SIGNAL(filterChanged(CommentStatus)), SLOT(filterChanged(CommentStatus)));
	QListView::setModel(model);
}

void CommentsView::resizeEvent(QResizeEvent *e)
{
	showCommandBar();
	return QListView::resizeEvent(e);
}

void CommentsView::showCommandBar()
{
	if (currentIndex().isValid()) {
		QRect rc = visualRect(currentIndex());
		QPoint pt = rc.bottomLeft();
		m_commandBar->resize(m_commandBar->sizeHint());
		QRect rcBar = m_commandBar->rect();
		pt.setX(pt.x() + COMMENT_PADDING);
		pt.setY(pt.y() - rcBar.height() - COMMENT_PADDING);
		m_commandBar->move(pt);
		m_commandBar->setVisible(true);
	}
	else {
		m_commandBar->setVisible(false);
	}
}

void CommentsView::contextMenuEvent(QContextMenuEvent * e)
{
	CommentFilterModel* m = static_cast<CommentFilterModel*>(model());
	CommentData::CommentStatus status = m->filterStatus();
	if (selectionModel()->hasSelection()) {
		QMenu * menu = new QMenu(this);
		MainWindow *mainwin = MainWindow::instance();
		if (status == CommentData::approve) {
			menu->addAction(mainwin->action("unapprove"));
			menu->addAction(mainwin->action("spam"));
			menu->addSeparator();
			menu->addAction(mainwin->action("reply"));
		}
		else if (status == CommentData::hold) {
			menu->addAction(mainwin->action("approve"));
			menu->addAction(mainwin->action("spam"));
			menu->addSeparator();
			menu->addAction(mainwin->action("reply"));
		}
		else if (status == CommentData::spam) {
			menu->addAction(mainwin->action("approve"));
			menu->addAction(mainwin->action("notspam"));
			menu->addSeparator();
		}
		menu->addAction(mainwin->action("edit"));
		menu->addSeparator();
		menu->addAction(mainwin->action("delete"));
		menu->exec(e->globalPos());
		e->accept();
		return;
	}
	QListView::contextMenuEvent(e);
}

void CommentsView::keyPressEvent(QKeyEvent * e)
{
	if (state() != EditingState && e->key() == Qt::Key_Delete && e->modifiers() == Qt::NoModifier) {
		MainWindow::instance()->action("delete")->trigger();
		e->accept();
	}
	QListView::keyPressEvent(e);
}

//////////////////////////////////////////////////////////////
// LineEdit
//////////////////////////////////////////////////////////////
LineEdit::LineEdit( QWidget *parent)
: QTextEdit(parent)
{
	setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 
	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

	setMinimumHeight(0);

	connect(this, SIGNAL(textChanged()), SLOT(recalculateSize()));
}

LineEdit::~LineEdit()
{
}

QSize LineEdit::minimumSizeHint() const
{
	QSize sh = QTextEdit::minimumSizeHint();
	sh.setHeight(fontMetrics().height() + 1);
	sh += QSize(0, QFrame::lineWidth() * 2);
	return sh;
}

QSize LineEdit::sizeHint() const
{
	QSize sh = QTextEdit::sizeHint();
	sh.setHeight(int(document()->documentLayout()->documentSize().height()));
	sh += QSize(0, QFrame::lineWidth() * 2);
	((QTextEdit*)this)->setMaximumHeight(sh.height());
	return sh;
}

void LineEdit::resizeEvent(QResizeEvent* e)
{
	QTextEdit::resizeEvent(e);
	QTimer::singleShot(0, this, SLOT(updateScrollBar()));
}

void LineEdit::recalculateSize()
{
	updateGeometry();
	QTimer::singleShot(0, this, SLOT(updateScrollBar()));
}

void LineEdit::updateScrollBar()
{
	setVerticalScrollBarPolicy(sizeHint().height() > height() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
	ensureCursorVisible();
}


